
/* GCSx
** ANIMGROUP.CPP
**
** Animation group support, objects and drawing
** Non-EDITOR members
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// Animation group components

AnimComponent::AnimComponent() { start_func
    tileset = NULL;
    index = 0;
    effects = 0;
    x = y = 0;
}

AnimFrame::AnimFrame() { start_func
    count = 0;
    xOrigin = yOrigin = 0;
    comps = NULL;
    xOffs = yOffs = 0;
    delay = 1;
}

AnimFrame::~AnimFrame() { start_func
    delete[] comps;
}

const AnimFrame& AnimFrame::operator=(const AnimFrame& from) { start_func
    count = from.count;
    xOrigin = from.xOrigin;
    yOrigin = from.yOrigin;
    xOffs = from.xOffs;
    yOffs = from.yOffs;
    delay = from.delay;
    delete[] comps;
    comps = NULL;
    if (count) {
        comps = new AnimComponent[count];
        memcpy(comps, from.comps, sizeof(AnimComponent[count]));
    }
    return from;
}

void AnimFrame::setCount(int newCount) { start_func
    AnimComponent* newComps = NULL;
    if (newCount) {
        newComps = new AnimComponent[newCount];
        // Copy existing
        memcpy(newComps, comps, sizeof(AnimComponent[min(count, newCount)]));
        // (any empty spaces initialized already)
    }
    delete[] comps;
    comps = newComps;
    count = newCount;
}

AnimSequence::AnimSequence() : name(blankString) { start_func
    count = 0;
    width = 0;
    height = 0;
    loopType = LOOP_NONE;
    frames = NULL;
}

AnimSequence::~AnimSequence() { start_func
    delete[] frames;
}

const AnimSequence& AnimSequence::operator=(const AnimSequence& from) { start_func
    width = from.width;
    height = from.height;
    count = from.count;
    loopType = from.loopType;
    delete[] frames;
    frames = NULL;
    if (count) {
        frames = new AnimFrame[count];
        for (int pos = 0; pos < count; ++pos) {
            frames[pos] = from.frames[pos];
        }
    }
    return from;
}

void AnimSequence::setCount(int newCount) { start_func
    AnimFrame* newFrames = NULL;
    if (newCount) {
        newFrames = new AnimFrame[newCount];
        // Copy existing
        for (int pos = min(count, newCount) - 1; pos >= 0; --pos) {
            newFrames[pos] = frames[pos];
        }
        // (any empty spaces initialized already)
    }
    delete[] frames;
    frames = newFrames;
    count = newCount;
}

// Animation groups

AnimGroup::AnimGroup(class World* myWorld, int myId) : name(blankString), nameL(blankString) { start_func
    assert(myWorld);
    
    id = myId;
    cached = 0;
    lockCount = 0;
    cacheFile = NULL;
    anims = NULL;
    numAnim = 0;
    defWidth = defHeight = IMAGE_DEFAULT_SIZE;
    world = myWorld;
}

AnimGroup::~AnimGroup() { start_func
    delete cacheFile;
}

int AnimGroup::usesTileSet(const class TileSet* tileset) const { start_func
    for (int pos = 0; pos < numAnim; ++pos) {
        for (int subpos = 0; subpos < anims[pos].count; ++subpos) {
            for (int subsubpos = 0; subsubpos < anims[pos].frames[subpos].count; ++subsubpos) {
                if (anims[pos].frames[subpos].comps[subsubpos].tileset == tileset) return 1;
            }
        }
    }
    
    return 0;
}
    
void AnimGroup::cacheLoad() throw_File { start_func
    if (cached) {
        // load into memory
        assert(cacheFile);
        assert(cacheFile->getVersion() <= 1);
        assert(anims == NULL);
        
        cacheFile->rewind();
        
        try {
            if (numAnim) {
                anims = new AnimSequence[numAnim];

                for (int pos = 0; pos < numAnim; ++pos) {
                    cacheFile->readStr(anims[pos].name);
                    anims[pos].count = cacheFile->readInt();
                    anims[pos].width = cacheFile->readInt();
                    anims[pos].height = cacheFile->readInt();
                    anims[pos].loopType = (AnimSequence::AnimLoop)cacheFile->readInt8();

                    // Validity checks
                    if ((anims[pos].count < 1) || (anims[pos].count > MAX_ANIMFRAME) ||
                        ((anims[pos].loopType != AnimSequence::LOOP_NONE) && (anims[pos].loopType != AnimSequence::LOOP_FORWARD) && (anims[pos].loopType != AnimSequence::LOOP_PINGPONG)) ||
                        (anims[pos].width < MIN_SPRITESIZE) || (anims[pos].width > MAX_SPRITESIZE) ||
                        (anims[pos].height < MIN_SPRITESIZE) || (anims[pos].height > MAX_SPRITESIZE)) {
                        throw FileException("Corrupted animation group content");
                    }

                    // Frames
                    anims[pos].frames = new AnimFrame[anims[pos].count];

                    for (int subpos = 0; subpos < anims[pos].count; ++subpos) {
                        anims[pos].frames[subpos].count = cacheFile->readInt();
                        anims[pos].frames[subpos].xOrigin = cacheFile->readInt16();
                        anims[pos].frames[subpos].yOrigin = cacheFile->readInt16();
                        anims[pos].frames[subpos].xOffs = cacheFile->readInt16();
                        anims[pos].frames[subpos].yOffs = cacheFile->readInt16();
                        anims[pos].frames[subpos].delay = cacheFile->readInt8();

                        // Validity checks
                        if ((anims[pos].frames[subpos].count < 0) || (anims[pos].frames[subpos].count > MAX_ANIMCOMP) ||
                            (anims[pos].frames[subpos].delay < 1)) {
                            throw FileException("Corrupted animation group content");
                        }
                        
                        // Components
                        // (no count- .comps is already NULL)
                        if (anims[pos].frames[subpos].count) {
                            anims[pos].frames[subpos].comps = new AnimComponent[anims[pos].frames[subpos].count];
    
                            for (int subsubpos = 0; subsubpos < anims[pos].frames[subpos].count; ++subsubpos) {
                                int tilesetId = cacheFile->readInt();
                                anims[pos].frames[subpos].comps[subsubpos].tileset = world->findTileSet(tilesetId);
    
                                // Ensure tileset exists
                                if (!anims[pos].frames[subpos].comps[subsubpos].tileset) {
                                    throw FileException("Corrupted animation group content");
                                }
    
                                anims[pos].frames[subpos].comps[subsubpos].index = cacheFile->readInt16();
                                anims[pos].frames[subpos].comps[subsubpos].x = cacheFile->readInt16();
                                anims[pos].frames[subpos].comps[subsubpos].y = cacheFile->readInt16();
                                anims[pos].frames[subpos].comps[subsubpos].r = cacheFile->readInt8();
                                anims[pos].frames[subpos].comps[subsubpos].g = cacheFile->readInt8();
                                anims[pos].frames[subpos].comps[subsubpos].b = cacheFile->readInt8();
                                anims[pos].frames[subpos].comps[subsubpos].alpha = cacheFile->readInt8();
                                anims[pos].frames[subpos].comps[subsubpos].effects = cacheFile->readInt8();
    
                                // Validity checks
                                if ((anims[pos].frames[subpos].comps[subsubpos].x >= anims[pos].width) ||
                                    (anims[pos].frames[subpos].comps[subsubpos].y >= anims[pos].height) ||
                                    (anims[pos].frames[subpos].comps[subsubpos].effects > AnimComponent::EFFECTS_ALL)) {
                                    throw FileException("Corrupted animation group content");
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (...) {
            // Recache ourselves
            delete[] anims;
            anims = NULL;
            throw;
        }
        
        // We retain this pointer and reuse it IF we
        // unlock to 0 AND aren't modified
        cached = 0;
    }
}

void AnimGroup::loadHeader(FileRead* file) throw_File { start_func
    if (file->getVersion() > 1) {
        throw FileException("Unsupported animation group version %d", file->getVersion());
    }

    file->readStr(name);
    nameL = name;
    toLower(nameL);
    id = file->readInt();
    defWidth = file->readInt();
    defHeight = file->readInt();
    numAnim = file->readInt();
    // Check validity
    if ((defWidth < MIN_SPRITESIZE) || (defHeight < MIN_SPRITESIZE) ||
        (defWidth > MAX_SPRITESIZE) || (defHeight > MAX_SPRITESIZE) ||
        (numAnim < 0) || (numAnim > MAX_ANIMGROUP) ||
        (!id)) {
        throw FileException("Corrupted animation group header");
    }
}

void AnimGroup::loadContent(FileRead* file) { start_func
    delete cacheFile;
    cacheFile = file;
    cached = 1;
}

int AnimGroup::isContentCached() const { start_func
    return cached;
}

int AnimGroup::markLock() throw_File { start_func
    cacheLoad();
    return ++lockCount;
}

int AnimGroup::markUnlock() { start_func
    --lockCount;
    assert(lockCount >= 0);
    if ((lockCount == 0) && (cacheFile)) {
        // Recache ourselves
        cached = 1;
        delete[] anims;
        anims = NULL;
    }
    return lockCount;
}

int AnimGroup::isLocked() const { start_func
    return lockCount;
}

